from pyArango.connection import Connection
from pyArango.collection import Collection, Edges, Field
from pyArango.graph import Graph, EdgeDefinition, getGraphClass
import json
import inspect
from ..orm import TagModel, getTagModelChildClass, getTagModelChildClassName, buildClass, getModelByName, regModuleClass
from datetime import datetime as dt
# from qdmtools.dictop import Dict2Obj
from pydantic import BaseModel, Field as ModField, create_model


class Dict2Obj(dict):
    """把字典变成对象，可以对字典使用.来调用属性"""
    def __getattr__(self, key):
        return self.get(key)

    def __setattr__(self, key, value):
        self[key] = value

# class Humans(Collection):
#     _fields = {
#         "name": Field()
#     }

GRAPH_CLS = \
'''class %s(Graph) :
    _edgeDefinitions = [
        %s
    ]
    _orphanedCollections = []
'''

EDGE_DEF = '''EdgeDefinition("%s", fromCollections=["%s"], toCollections=["%s"])'''

atags = []
arefs = []

def maketreepath(a):
    b = [ai.split("/") for ai in a]
    c = [ai.split("/") for ai in a]
    for bi in range(len(b)):
        t = [ci for ci in c if ci[1]==b[bi][0]]
        while len(t)>0:
            b[bi].insert(0, t[0][0])
            t = [ci for ci in c if ci[1]==b[bi][0]]
            if len(t)==0:
                break 
    return ["/".join(bl) for bl in b]

def zipmap(name, m): 
    stru = {"name": name}
    stru.update(m)
    # print("}}", name, m, stru)
    return stru
def vtype(t):
    if t=="number":
        return "float"
    elif t=="string":
        return "str"
    elif t=="integer":
        return "int"
    elif t=="array":
        return "list"
    return t
# def vtype(t):
#     if t=="number":
#         return "float"
#     elif t=="string":
#         return "str"
#     elif t=="integer":
#         return "int"

# def buildClass(meta):
#     sch = meta["schema"]
#     fields = {}
#     for k,v in sch["properties"].getStore().items():
#         fields[k] = (vtype(v["type"]), Field(..., title=v["title"]))
#     # print(meta.name, meta.extend)
#     pkglst = meta.extend.split(".")
#     exec(f"from {'.'.join(pkglst[0:-1])} import {pkglst[-1]}")
#     return create_model(meta.name, __base__=eval(pkglst[-1]), **fields)

class ArangoDrv:
    def __init__(self, path, tags, refs):
        confs = path.split("@")
        uname,pwd = confs[0].split("/")
        dbname,gname = confs[-1].split("/")
        param = {"username":uname, "password":pwd}
        if len(confs)>2 :
            param["arangoURL"] = confs[1]
        self.conn = Connection(**param)
        # self.econn = EdgeConnection(username=uname, password=pwd)
        self.db = self.conn[dbname]
        
        for tag in tags:
            self.createTag(tag)
            regModuleClass(tag)
        for ref in refs:
            self.createRef(ref)

        edgedefs = []
        for ref in refs:
            fromcls = ref.__fields__["src"].type_.__name__.capitalize()
            tocls = ref.__fields__["tar"].type_.__name__.capitalize()
            print(fromcls, "==>",  tocls)
            edgedefs.append(EDGE_DEF % (ref.schema()["title"], fromcls, tocls))
        gc = GRAPH_CLS % (gname, ",".join(edgedefs))
        # print(gc)
        exec(gc)

        # graphclass = globals()[gname]
        if self.db.hasGraph(gname):
            self.graph = self.db.graphs[gname]
        else:
            self.graph = self.db.createGraph(gname)
        
        self.gname = gname

        tagdocs = self.db["Tags"].fetchAll()
        #构建继承树
        exttree = {}
        for tagdoc in tagdocs:
            name = tagdoc.name
            par = tagdoc.extend.split(".")[-1]
            exttree[f"{par}/{name}"] = tagdoc
        
        paths = maketreepath(exttree.keys())
        orderpaths = sorted(paths)

        # print(orderpaths)

        taglst = []
        for pa in orderpaths:
            key = "/".join(pa.split("/")[-2:])
            # print("k"*50, key)
            tagdoc = exttree[key]
            # url=(str,...), name=(str, ...), user=(str, ...), pwd=(str, ...)
            taglst.append(buildClass(tagdoc))

        # print(self.graph._edgeDefinitions)
        # nodesCollection = self.db.createCollection(name="nodes")
        # self.nodes = self.db["nodes"]
        # self.idnow = 0

    # def newid(self):
    #     self.idnow+=1
    #     yield self.idnow
        
    def createTag(self, mod):
        name = mod.schema()["title"]
        # print(mod.schema()["properties"].keys(), mod._type)
        # if mod.modelType!="":
        #     typename = mod.modelType
        # methods = mod.schema()
        typename = getTagModelChildClassName(mod)
        # print(name, typename)
        if name==typename:
            try:
                fields = [fname for fname,fset in mod.schema()["properties"].items()]
                # print(fields)
                atags.append(type(name, (Collection,), {"_fields":{fn:Field() for fn in fields}}))
                tag = self.db.createCollection(name)
                print("新建", tag)
            except:
                print("已存在", name, "*"*10)
                tag = self.db[name]
        else:
            tags = self.db["Tags"]
            tagnames = [t.name for t in self.db["Tags"].fetchAll()]
            if name not in tagnames:
                newdoc = tags.createDocument()
            else:
                newdoc = tags.fetchByExample({"name":name}, batchSize=20)[0]
            newdoc["schema"] = mod.schema()
            newdoc["name"] = name
            newdoc["extend"] = f"{inspect.getmodule(mod.__base__).__name__}.{mod.__base__.__name__}"
            newdoc.save()

            # print("==>", getTagModelChildClassName(mod))
            tag = self.db[getTagModelChildClassName(mod)]
        return tag   #tag.id

    def updateTag(self, mod):
        name = mod.schema()["title"]
        typename = getTagModelChildClassName(mod)
        # if name==typename:
        #     tag = self.db[name]
        # else:
        tags = self.db["Tags"]
        tagnames = [t.name for t in self.db["Tags"].fetchAll()]
        if name not in tagnames:
            newdoc = tags.createDocument()
        else:
            newdoc = tags.fetchByExample({"name":name}, batchSize=20)[0]
        newdoc["schema"] = mod.schema()
        newdoc["name"] = name
        newdoc["extend"] = f"{inspect.getmodule(mod.__base__).__name__}.{mod.__base__.__name__}"
        newdoc.save()

        # print("==>", getTagModelChildClassName(mod))
        tag = self.db[getTagModelChildClassName(mod)]
        return tag 

    def getTag(self, tagname):
        # print(">"*30, tagname)
        tagtree = [t for t in tagname.split("/") if t!=""]
        qmaps = [{"name":tagi} for tagi in tagtree] 
        # if len(tagtree)>1:
        #     qmap["schema"]={}
        #     qmap["schema.description"] = tagname if tagname.startswith("/") else "/"+tagname
        tagexts = []
        for qmap in qmaps:
            # print("X"*30, qmap)
            tags = self.db["Tags"].fetchByExample(qmap, batchSize=20)
            if len(tags)>0:
                tagexts.append(tags[0])
            else:
                return None

        #保证继承树正常
        bc = None
        for tag in tagexts:
            # tag = tags[0]
            # print("tag:::", tag)
            bc = buildClass(tag)
        return bc

    def _getChildTag(self, tag, resarr):
        aql = f"FOR t IN Tags  FILTER LIKE ( t.extend, '%{tag}') RETURN t"
        res = [it for it in self.db.AQLQuery(aql, rawResults=False, batchSize=1000)]
        if len(res)>0:
            for r in res:
                resarr.append(r)
                self._getChildTag(r.name, resarr)

    def getTagTree(self, mod):
        '''获取到所有子类'''
        tagname = mod.__name__
        # qmaps = [{"name":tagi} for tagi in tagtree] 

        tagexts = []
        self._getChildTag(tagname, tagexts)
        print("获取所有子类名为：", [t.name for t in tagexts])

        bc = [mod]
        for tag in tagexts:
            bc.append(buildClass(tag))
        return bc

    def getTags(self):
        tags = self.db["Tags"].fetchAll()
        taglst = []
        for tag in tags:
            # print("tag", ":"*30, tag)
            # url=(str,...), name=(str, ...), user=(str, ...), pwd=(str, ...)
            taglst.append(buildClass(tag))
        return taglst

    def delTag(self, tagname):
        tag = self.db["Tags"].fetchByExample({"name":tagname}, batchSize=20)[0]
        tag.delete()
        return tag._id

    def delNode(self, doid):
        doc = self._di(doid)
        doc.delete()
        return doid

    def createRef(self, rmod):
        # gcls = getGraphClass(self.gname)
        rname = rmod.schema()["title"]
        # fromcls = rmod.__fields__["src"].type_.__name__.capitalize()
        # tocls = rmod.__fields__["tar"].type_.__name__.capitalize()
        # print(rname, fromcls, tocls)
        # gcls._edgeDefinitions.append(EdgeDefinition(rname, fromCollections=[fromcls], toCollections=[tocls]))
        fields = [fname for fname,fset in rmod.schema()["properties"].items() if fname!="src" and fname!="tar"]
        arefs.append(type(rname, (Edges,), {"_fields":{fn:Field() for fn in fields}}))
        try:
            ed = self.db.createCollection("Edges", name=rname)
        except:
            print("已存在", rname, "*"*10)
            ed = self.db[rname]
        return ed

    def getRef(self, refname):
        # gcls = getGraphClass(self.gname)
        ed = self.db[refname]
        return ed

    def createNode(self, nodetype, name=None, tags=None, data=None):
        clsname = data.schema()["title"]
        name = nodetype
        nodes = self.db[name]
        newdoc = nodes.createDocument()
        mj = json.loads(data.json())
        # print(mj)
        for k,v in mj.items():
            if k.startswith("linkfrom_") or k.startswith("linkto_"): #如果是linkfrom或者linkto的，是需要进行构建edge的特殊字段
                continue
            newdoc[k] = v
        newdoc["clstype"] = clsname
        newdoc.save()
        self.makelinks(newdoc._id, mj)
        # newdoc = self.graph.createVertex(name, mj)
        #db.add_node({'name': 'Steve Wozniak', 'type':['person','engineer','founder']})
        return newdoc._id

    def updateNode(self, nodeid, **args):
        # name = args["name"].schema()["title"]
        # doc = self.db[name][nodeid]
        doc = self._di(nodeid)
        # mj = json.loads(args.json())
        for k,v in args.items():
            if k.startswith("linkfrom_") or k.startswith("linkto_"): #如果是linkfrom或者linkto的，是需要进行构建edge的特殊字段
                continue
            doc[k] = v
        doc.save()
        self.makelinks(nodeid, args)
        
        return True

    def makelinks(self, nodeid, args):
        for key in args.keys():
            if key.startswith("linkfrom_"):
                #创建父节点连接
                lf, et = key.split("_") 
                pid = args[key]
                self.createEdge(et, pid, nodeid, method=et)
            if key.startswith("linkto_"):
                #创建子节点连接
                lf, et = key.split("_") 
                cid = args[key]
                self.createEdge(et, nodeid, cid, method=et)

    def doc2obj(self, doc, clstype):
        # print(doc, {key:doc[key] for key in doc})
        # print(doc.getStore())
        
        try:
            st = doc.getStore()
            # print("doc2obj.clstype==>", doc.clstype)
            cls = getModelByName(doc.clstype)
            # print("获取到模型对象:", cls.schema(), "==>", cls.__base__)
            # print("转换数据对象:", clstype.schema(), "/", st)
            obj = cls.parse_obj(st)
            # obj = Dict2Obj(st.dict())
            # for k in doc:
            #     obj[k] = doc[k]
            obj.setdoid(doc._id)
            
            # print("/"*100, st, "_code_type" in st)
            if "_code_type" in st:
                codedict = {}
                for k in st.keys():
                    if k.startswith("_code_"):
                        codedict[k[6:]] = st[k]
                obj.setcode(codedict)
            if "_code" in st:
                codes = [{"type":"beidou", "codeval":c, "level":lev} for c,lev in st["_code"].items()]
                obj.setcode(codes)

            # obj["clstype"] = clstype.schema()["title"]
            return obj
        except Exception as e:
            print(e, doc._id)
            return None
        
    # def _mkfield(self, clstype, key):
    #     return f"`{key}`"
    
    # def _mkaql(self, nodetype, qargs):
    #     if "$and" in qargs or "$or" in qargs:
    #         key = qargs.keys()[0]
    #         return key.join([self._mkaql( nodetype, qi) for  qi in qargs[key]])
    #     else:
    #         return {self._mkfield(nodetype, key):equ for key,equ in qargs.items()}

    def getNodes(self, nodetypes, **args):
        # name = args["name"]
        if type(nodetypes)==list:
            nodetype = nodetypes[0]
        else:
            nodetype = nodetypes

        clsname = nodetype.schema()["title"]
        inftype = getTagModelChildClass(nodetype)
        name = inftype.schema()["title"]
        # if args is not None and args.get("withCode") is not None:
        #     asql = args["querybymodule"].getFilter()
        #     result = self.db.AQLQuery(asql, rawResults=False, batchSize=1000)
        if args is not None and args.get("querybymodule") is not None:
            qargs = {}
            if "query" in args and args["query"] is not None:
                qargs = args["query"]
                if type(qargs)==str:
                    qargs = {"_aql":qargs}
            #     qargs =  self._mkaql(nodetype, args["query"])# 转换字段，为中文字段加``
            # print(args)
            if "_limit" in args.keys():
                qargs["_limit"] = args["_limit"]
            if "_skip" in args:
                qargs["_skip"] = args["_skip"]
            asql = args["querybymodule"].getFilter(**qargs)
            argvs = {"rawResults":False, "batchSize":1000}
            result = self.db.AQLQuery(asql, **argvs)
            print("AQL",">"*20, asql)
        else:
            if args is None or args.get("query") is None:
                # result = self.db[name].fetchAll() # skip=0, limit=2
                queryargs = {}
                queryargs["clstype"] = clsname
            else:
                # for doc in query:
                #     print(doc)
                queryargs = args["query"]
                queryargs["clstype"] = clsname
            print("getNodes", queryargs)
            argvs = {"batchSize":1000, "count":True}
            # argvs["limit"]=10
            # argvs["skip"]=0
            result = self.db[name].fetchByExample(queryargs, **argvs) # 
        #print query.count # print the total number or documents
        # print(nodetype, args, name, result)
        # print(result)
        # for ri in result:
        #     print(ri)
        # print(result)
        
        # if type(nodetypes)==list:
        #     fields = {}
            
        #     for mod in nodetypes:
        #         fkeys = [zipmap(fname, fset) for fname,fset in mod.schema()["properties"].items()]
        #         for field in fkeys:
        #             fields[field["name"]] = (eval(vtype(field["type"])), ModField(None, title=field["title"])) #所有都设置不必必填
        #     nodetype1 = create_model(clsname, __base__=nodetype.__base__, **fields)
        # else:
        #     nodetype1 = nodetypes
        nodetype1 = None
        # print(nodetype1.schema())
        return [r1 for r1 in [self.doc2obj(r, nodetype1) for r in result if r is not None] if r1 is not None]
    
    def getNode(self, querynode, **query):
        # name = args["name"]
        clsname = querynode.__class__.__base__.schema()["title"]
        inftype = getTagModelChildClass(querynode.__class__.__base__)
        name = inftype.schema()["title"]
        
        # queryargs = {}
        # for fname in querynode.schema()["properties"].keys():
        #     # print(fname)
        #     queryargs[fname] = querynode[fname]
        queryargs = querynode.getFilter()
        queryargs["clstype"] = clsname
        for k,v in query.items():
            queryargs[k] = v
        print("查询语句",queryargs)
        result = self.db[name].fetchByExample(queryargs, batchSize=2, count=True)
        rs = [self.doc2obj(r, querynode.__class__.__base__) for r in result]
        # for r in rs:
        #     r.doid = 123
        if len(rs)>0:
            ret = [r1 for r1 in rs if r1 is not None]
        else:
            ret = []
        return None if len(ret)==0 else ret[0]

    def createEdge(self, edge, node1id, node2id, **args):
        # https://kkgithub.com/ArangoDB-Community/pyArango
        if type(edge)==str:
            edgename = edge
        else:
            edgename = edge.name#self.getRef(edge)
        # name = edge.__class__.schema()["title"]
        # conn = self.db[name].createEdge()

        # conn.links(node1id, node2id)
        # for a, v in args.items():
        #     conn["a"] = v
        # conn.save()
        # return conn._id
        if args is None:
            args = {}
        # # linking them
        ln = self.graph.link(edgename, node1id, node2id, args)
        return ln._id

    def deleteEdge(self, edgetype, node1id, node2id):
        # # linking them
        lns = self.db[edgetype].fetchByExample({"_from":node1id, "_to":node2id}, batchSize=100, count=True)
        dels = []
        for ln in lns:
            did = ln._id
            ln.delete()
            # ln["old"] = True
            # ln["deltime"] = int(dt.now().timestamp()*1000)
            # ln.save()
            dels.append(did)
        return dels

    def getEdge(self, etype, src, tar):
        return None

    def _di(self, nid):
        coll,key = nid.split("/")
        doc = self.db[coll][key]
        return doc

    def di(self, nid):
        coll,key = nid.split("/")
        doc = self.db[coll][key]
        clsname = getModelByName(doc.clstype)
        # tab = getModelByName(clsname)
        # print(doc,doc.clstype,clsname)
        return self.doc2obj(doc, clsname)

    def getNexts(self, nid, etype):
        coll,key = nid.split("/")
        doc = self.db[coll][key]
        es = doc.getOutEdges(self.db[etype])
        # print(es)
        nexts = []
        for e in es:
            co,k = e._to.split("/")
            ndoc = self.db[co][k]
            nclsname = getModelByName(ndoc.clstype)
            nexts.append(self.doc2obj(ndoc, nclsname))
        return nexts

    def getLasts(self, nid, etype):
        coll,key = nid.split("/")
        doc = self.db[coll][key]
        es = doc.getInEdges(self.db[etype])
        # print(es)
        nexts = []
        for e in es:
            co,k = e._from.split("/")
            ndoc = self.db[co][k]
            nclsname = getModelByName(ndoc.clstype)
            nexts.append(self.doc2obj(ndoc, nclsname))
        return nexts

    def query(self, q):
        return #self._graph.search(q)
    
    def count(self, tag, filter=None):
        if type(tag)==str:
            tagname = tag
        else:
            tagname = tag.schema()["title"]
        mod = self.getTag(tagname)
        
        if mod is not None:
            basemod = mod.__base__
            while basemod.__base__.schema()["title"]!="TagModel":
                basemod = basemod.__base__  
            basemod = basemod.schema()["title"]
        else:
            basemod = tagname

        # print(tagname, basemod)

        if tagname==basemod and filter is None:
            asql = f"return length({basemod})"
        elif tagname!=basemod and filter is None:
            asql = f"return length(FOR doc IN {basemod} filter doc.clstype=='{tagname}' RETURN 1)"
        else:
            asql = f"return length(FOR doc IN {basemod} filter doc.clstype=='{tagname}' and {filter} RETURN 1)"
        # print(asql)
        argvs = {"rawResults":False, "batchSize":100}
        result = self.db.AQLQuery(asql, **argvs)
        rs = str(result)
        # print(rs)

        _count = int(rs[1:-1])
        if _count==0 and self.db.hasCollection(tagname):
            result = self.db.AQLQuery(f"return length({tagname})", **argvs)
            rs = str(result)
            # print(rs)
            _count = int(rs[1:-1])
        return _count



    def plot(self):
        pass